Skip to content

feat(desktop): re-land sidebar filter/sort with activity-based ordering - #6005

Closed
AviPeltz wants to merge 5 commits into
mainfrom
reland-sidebar-sort-filter
Closed

AviPeltz wants to merge 5 commits into
mainfrom
reland-sidebar-sort-filter

Conversation

@AviPeltz

@AviPeltz AviPeltz commented Jul 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Re-lands the dashboard sidebar filter + sort feature (#5956, reverted in #5996) with the fixes that motivated the revert:

  1. Reapply feat(desktop): filter and sort projects in the dashboard sidebar #5956 — pure revert-of-revert; the filter input, sort menu, and preference plumbing come back unchanged. Applies cleanly over fix(desktop): make sidebar drag overlays transparent over black #5997/fix(desktop): make sidebar project ordering deterministic and prepend new projects #5999/fix(desktop): keep projects list expanded when using the add-repo menu #6000 (the fix(desktop): keep projects list expanded when using the add-repo menu #6000 stopPropagation guard survives).
  2. Crash fix — Electric cloud-fallback rows carry ISO-string timestamps at runtime (the shape parser doesn't handle timestamptz, despite the SelectV2Workspace type). They're normalized to real Dates where they enter mergeHostWorkspaces, and the sort coerces defensively (toTime, NaN-safe tie-breaks) so bad data can never crash the sidebar again. Fixes the workspace.updatedAt.getTime is not a function crash.
  3. Workspace-level sorting — non-manual modes now sort workspaces inside each project and inside sections, rank sections among loose workspaces by their newest member, and keep the local main workspace pinned first.
  4. "Last updated" means real activity — the revert reason: v2_workspaces.updatedAt only moves on metadata writes, so messaging an agent never reordered anything. The sidebar now polls each reachable host's terminalAgents.list (10s interval, only while the sort mode is "Last updated") and ranks everything by max(updatedAt, newest agent hook event). Sending a Claude message reorders within ~10s.
  5. Design doc — apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md records the signal semantics, the polling trade-off, and the follow-up paths (event-bus push for instant reordering; cloud-persisted activity as the long-term model; Electric timestamp-parser root fix as its own PR).

Verification

  • Full desktop suite: 2305 pass / 0 fail (new tests for string-date coercion, agent-activity ranking at workspace/section/project levels, merge normalization).
  • Live via CDP against the dev app: sort menu + filter exercised end-to-end; rendered order matches the ranking computed from real host binding data and provably diverges from updatedAt-only ordering; a synthetic UserPromptSubmit on a 3-day-idle workspace moved it to the top of its project in ~4s.
  • Typecheck and lint clean.

Notes

https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS


Open in Stage

Summary by cubic

Re-lands the dashboard sidebar filter and sort, and makes “Last updated” use real agent activity so recent conversations reorder projects and workspaces. Also fixes a crash from cloud string timestamps.

  • New Features

    • Added project filter input and sort menu in the sidebar header; choice is saved in sidebarProjectSortMode.
    • “Last updated” now ranks by max(updatedAt, latest agent event), polling each reachable host’s terminalAgents.list every 10s only while this mode is active. Applies at workspace, section, and project levels; the local main workspace stays first.
    • Non-manual modes sort workspaces within projects and within sections; sections rank among loose workspaces by their newest member.
    • Drag-and-drop is disabled when sort ≠ manual or a filter is active to avoid corrupting hidden items; filter expands matched projects/sections and shows a simple “no match” state.
  • Bug Fixes

    • Prevented sidebar crash by coercing Electric cloud-fallback createdAt/updatedAt strings to Date in mergeHostWorkspaces; sorting uses NaN-safe coercion with name/id tie-breakers.
    • Standardized automation failure colors to bg-destructive.

Written for commit be67fa8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added sidebar project sorting by manual order, creation date, or recent activity.
    • Added project and workspace filtering with matching results automatically expanded.
    • Added persistent sorting preferences and improved controls in the sidebar header.
    • Recent agent activity now contributes to “Last updated” ordering.
  • Bug Fixes

    • Improved handling of cloud workspace timestamps.
    • Prevented drag-and-drop reordering while filtering or using non-manual sorting.
    • Standardized failed automation status styling.

AviPeltz added 5 commits July 27, 2026 15:29
Electric v2Workspaces rows carry ISO-string timestamps at runtime (the
shape parser doesn't handle timestamptz), despite the SelectV2Workspace
type saying Date. The new sidebar sort was the first code to call
.getTime() on workspace.updatedAt, so cloud-fallback rows (unreachable
host with no snapshot) crashed the whole dashboard render.

Convert cloud rows to real Dates where they enter mergeHostWorkspaces so
the type holds for every consumer, and make sortDashboardSidebarProjects
coerce timestamps defensively — garbage data now falls through to the
name/id tiebreakers instead of throwing mid-render.

Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
…sort

"Last updated" / "Date created" previously reordered only the project
groups; each project's children kept their manual tabOrder. Now workspaces
sort by the mode inside each project, workspaces inside sections sort too,
and sections rank among loose workspaces by their most recent activity
("updated") or their own createdAt ("created"). The local main workspace
stays pinned first, matching its treatment elsewhere in the sidebar.

Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
v2_workspaces.updatedAt only moves on metadata writes, so sending an
agent a message never reordered the sidebar. Each host's terminal-agent
bindings record lastEventAt per workspace; the sidebar now polls
terminalAgents.list per reachable host (only while the sort mode is
'updated'), threads the newest event time onto each workspace row, and
sorts workspaces, sections, and projects by max(updatedAt, agent
activity).

Claude-Session: https://claude.ai/code/session_01SSUQYFamkYAjs5sDQC5RLS
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The sidebar adds persisted manual, created, and updated sorting, project filtering, activity-aware ordering, timestamp normalization, and drag restrictions during filtered views. Automation failure indicators now use destructive theme tokens.

Changes

Dashboard sidebar sorting and activity

Layer / File(s) Summary
Persisted sort preference and sidebar controls
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, apps/desktop/src/renderer/hooks/useV2UserPreferences/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/...
Adds persisted sort modes, controlled sorting/filtering controls, query expansion behavior, and menu interactions.
Project filtering and ordering
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
Filters nested projects and sections, expands matching results, and sorts projects and children by manual, created, or activity timestamps with stable tie-breaking.
Agent activity data and timestamp normalization
apps/desktop/src/renderer/hooks/host-workspaces/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/..., apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md
Normalizes cloud timestamps, polls host terminal-agent activity in updated mode, decorates workspace rows, and documents the activity-sort behavior and limitations.
Filtered sidebar drag behavior
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/...
Disables outer and nested drag operations when filtering or non-manual sorting could make reordered data incomplete or inconsistent.

Automation failure status styling

Layer / File(s) Summary
Failure status color tokens
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/...
Replaces red-specific failure colors with the shared destructive color token for failed automation statuses.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Sidebar
  participant SidebarData
  participant HostTerminalAgents
  participant ProjectBuilder
  Sidebar->>SidebarData: select updated sorting
  SidebarData->>HostTerminalAgents: poll agent activity
  HostTerminalAgents-->>SidebarData: return latest lastEventAt
  SidebarData->>ProjectBuilder: pass workspace activity map
  ProjectBuilder-->>Sidebar: render sorted projects
Loading

Possibly related PRs

Suggested reviewers: kitenite

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, conventional, and accurately captures the main sidebar filter/sort re-land with activity-based ordering.
Description check ✅ Passed The description is detailed and covers the change, rationale, and verification, matching the template’s intent closely.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch reland-sidebar-sort-filter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ghost

ghost commented Jul 27, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md (1)

44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a language to the fenced code block (markdownlint MD040).

Static analysis flags this fence for missing a language specifier.

📝 Proposed fix
-```
+```text
 max(updatedAt, newest lastEventAt across the workspace's agent bindings)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md around lines 44 - 46, Add the
text language specifier to the fenced code block containing the max(updatedAt,
newest lastEventAt across the workspace's agent bindings) expression, preserving
its content unchanged.


</details>

<!-- cr-comment:v1:ef6f166a081adf59a846c384 -->

_Source: Linters/SAST tools_

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
@apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts:

  • Around line 205-215: The setSidebarProjectSortMode callback currently inserts
    DEFAULT_V2_USER_PREFERENCES when the live query has not hydrated. Destructure
    and use isReady from useLiveQuery, allowing the insert only when readiness is
    confirmed; otherwise defer the write while preserving the existing update path
    for an available preference row.

In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx:

  • Line 294: Update the nested drag-and-drop configuration at isInnerDragDisabled
    in DashboardSidebar so nested workspace/section dragging is disabled whenever
    filters are active or the current sorting mode is not manual. Reuse the existing
    isDragDisabled state for this prop to keep behavior consistent with the outer
    drag controls.

In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts:

  • Around line 32-43: Update getProjectActivityTimestamp to filter out NaN values
    from workspaces.map(getWorkspaceActivityTime) before applying Math.max, matching
    the existing getChildTimestamp section-level behavior. Preserve the fallback
    handling for projects without workspaces and ensure valid workspace timestamps
    still determine the project activity when another workspace is unparseable.

Nitpick comments:
In @apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md:

  • Around line 44-46: Add the text language specifier to the fenced code block
    containing the max(updatedAt, newest lastEventAt across the workspace's agent
    bindings) expression, preserving its content unchanged.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `047edc39-4001-4ba5-bf59-b21cd22b2e5c`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0f4656620f34e1d508f9da02ef16bf2714c3ba99 and be67fa804f87166caee96e936552a7d9d3c61b2e.

</details>

<details>
<summary>📒 Files selected for processing (27)</summary>

* `apps/desktop/docs/SIDEBAR_ACTIVITY_SORT.md`
* `apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.utils.test.ts`
* `apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.utils.ts`
* `apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/$automationId/components/PreviousRunsList/PreviousRunsList.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/components/AutomationRow/AutomationRow.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/DashboardSidebarProjectSection.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarProjectSection/components/DashboardSidebarExpandedProjectContent/DashboardSidebarExpandedProjectContent.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/DashboardSidebarWorkspacesHeader.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsFilterInput/DashboardSidebarProjectsFilterInput.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsFilterInput/index.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsSortMenu/DashboardSidebarProjectsSortMenu.tsx`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspacesHeader/components/DashboardSidebarProjectsSortMenu/index.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.test.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useSidebarDnd/useSidebarDnd.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/filterDashboardSidebarProjects.test.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/filterDashboardSidebarProjects.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/filterDashboardSidebarProjects/index.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/index.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.test.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts`
* `apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/testProjectFixtures.ts`
* `apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +205 to +215
const setSidebarProjectSortMode = useCallback(
(next: SidebarProjectSortMode) => {
const existing = collections.v2UserPreferences.get(
V2_USER_PREFERENCES_ID,
);
if (!existing) {
collections.v2UserPreferences.insert({
...DEFAULT_V2_USER_PREFERENCES,
sidebarProjectSortMode: next,
});
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'useLiveQuery\(' apps/desktop/src/renderer
rg -n -C5 '\b(isReady|isLoading)\b' apps/desktop/src/renderer
rg -n -C5 'setSidebarProjectSortMode|v2UserPreferences\.(insert|update)' apps/desktop/src/renderer

Repository: superset-sh/superset

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target hook outline/size =="
wc -l apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts
ast-grep outline apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts || true

echo "== target file relevant sections =="
sed -n '1,260p' apps/desktop/src/renderer/hooks使用V2UserPreferences/useV2UserPreferences.ts | cat -n 2>/dev/null || true

echo "== all occurrences of useV2UserPreferences =="
rg -n -C3 'useV2UserPreferences|useV2Project' apps/desktop/src/renderer | head -n 200

Repository: superset-sh/superset

Length of output: 29475


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file relevant sections =="
sed -n '1,238p' apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts | cat -n

echo "== package versions =="
node - <<'JS'
const fs = require('fs');
for (const f of ['package.json','apps/desktop/package.json']) {
  if (!fs.existsSync(f)) continue;
  const p = JSON.parse(fs.readFileSync(f,'utf8'));
  console.log(f, {
    tanstackReactDb: (p.dependencies?.['`@tanstack/react-db`'] || p.devDependencies?.['`@tanstack/react-db`']) &&
      p.dependencies?.['`@tanstack/react-db`'] || p.devDependencies?.['`@tanstack/react-db`']
  });
}
JS

echo "== nearby hook live query consumers (narrow) =="
rg -n -C2 'setSidebarProjectSortMode|useV2UserPreferences\s*\(\)' \
  apps/desktop/src/renderer/routes/_authenticated/_dashboard/components \
  apps/desktop/src/renderer/routes/_authenticated/_dashboard \
  apps/desktop/src/renderer/screens \
  | head -n 220

Repository: superset-sh/superset

Length of output: 21665


🌐 Web query:

@tanstack/react-db useLiveQuery isReady isLoading hydration cache data

💡 Result:

In the context of the TanStack ecosystem, @tanstack/react-db and @tanstack/react-query serve different primary purposes, though TanStack DB is built on top of TanStack Query [1]. useLiveQuery is a hook provided by @tanstack/react-db for managing reactive, database-like collections [2][3][1]. It automatically re-runs when the underlying data in a collection changes, eliminating the need for manual cache invalidation [1]. Key concepts regarding your query include: Loading States (isLoading) In useLiveQuery, the isLoading property is a boolean that indicates whether the query is currently performing its initial fetch [3]. Like TanStack Query's useQuery, this state is typically used to render loading indicators [2][3]. Hydration and Cache Because TanStack DB uses TanStack Query's caching and synchronization mechanisms under the hood, it interacts with the same underlying infrastructure [1]. 1. Hydration: This is the process of transferring prefetched state from the server to the client [4][5]. It uses the same hydrate and dehydrate utilities as TanStack Query to restore state into the QueryClient cache [4][5]. 2. Cache: Data fetched by useLiveQuery is stored in the TanStack Query cache [1]. When hydrating, TanStack Query intelligently merges this state with any existing data in the cache based on timestamps [4][5]. isReady There is no standard isReady property in the primary TanStack Query or TanStack DB hook return objects. If you are seeing this, it may be a custom implementation, a legacy property, or a misidentification of states like isPending or isSuccess [6]. The core statuses used by the TanStack ecosystem to determine if data is available are isPending, isError, and isSuccess [6]. Compatibility Note If you are migrating from @tanstack/react-query to @tanstack/react-db, note that while they share concepts, the APIs differ [7]. useLiveQuery replaces the standard useQuery pattern for collection management [7]. When dealing with SSR and hydration, ensure you are prefetching data on the server and using the HydrationBoundary or hydrate utilities on the client to avoid state mismatches [8][9][4]. Top Results: [2], [3], [1], [4], [5]

Citations:


🌐 Web query:

docs TanStack DB useLiveQuery API return { data, isReady isLoading }

💡 Result:

Yes, the useLiveQuery hook in TanStack DB returns an object that includes the properties { data, isReady, isLoading }, among others [1][2][3]. Specifically, the hook returns an object with the following shape [1][2]: - data: The result of the query. Its type is InferResultType (or an array, depending on the query) and it can be undefined if the query is not yet ready or is disabled [4][1][5]. - isLoading: A boolean indicating if the query is in a loading state [1][2][3]. - isReady: A boolean indicating if the query has finished its initial load and is ready to display [1][2]. In addition to these, the returned object typically includes several other useful properties for managing query state [1][2]: - status: A string representing the current status of the query (e.g., 'loading', 'error', 'success', 'disabled') [1][2][3]. - isError: A boolean indicating if the query encountered an error [1][2]. - isIdle: A boolean indicating if the query is idle [1][2]. - isCleanedUp: A boolean indicating if the query has been cleaned up [1][2]. - collection: The collection object associated with the query [1][2]. - state: A Map containing the reactive state of the query [1][2]. You can destructure these properties directly from the result of the hook [2][3]. Returning undefined or null from the useLiveQuery callback function will disable the query, in which case the status will be 'disabled' and data will be undefined [2][3].

Citations:


Wait for strict live-query readiness before writing user preferences.

useLiveQuery is only destructured for data, so isReady is ignored when collections.v2UserPreferences.get(...) returns no row while hydration is in progress. In that state, setSidebarProjectSortMode can insert a default preference before the persisted row is available. Gate the insert on isReady (and otherwise defer the write) so a concurrent server/provided row cannot collide or be lost.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts`
around lines 205 - 215, The setSidebarProjectSortMode callback currently inserts
DEFAULT_V2_USER_PREFERENCES when the live query has not hydrated. Destructure
and use isReady from useLiveQuery, allowing the insert only when readiness is
confirmed; otherwise defer the write while preserving the existing update path
for an available preference row.

Source: Coding guidelines

isCollapsed={isCollapsed}
isDraggingProject={activeProject != null}
isDragDisabled={isDragDisabled}
isInnerDragDisabled={isFilterActive}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disable nested DnD for non-manual sorting too.

Line 294 only disables workspace/section dragging while filtering. In created and updated modes, child rows are sorted independently, so a drop persists a manual order that immediately appears to revert and resurfaces when switching back to Manual. Pass isDragDisabled here.

As per PR objectives, nested workspace dragging must be disabled in filtered or non-manual views.

Proposed fix
- isInnerDragDisabled={isFilterActive}
+ isInnerDragDisabled={isDragDisabled}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isInnerDragDisabled={isFilterActive}
isInnerDragDisabled={isDragDisabled}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx`
at line 294, Update the nested drag-and-drop configuration at
isInnerDragDisabled in DashboardSidebar so nested workspace/section dragging is
disabled whenever filters are active or the current sorting mode is not manual.
Reuse the existing isDragDisabled state for this prop to keep behavior
consistent with the outer drag controls.

Comment on lines +32 to +43
// The host only bumps a project's own updatedAt on metadata patches (e.g.
// rename), so "recent activity" comes from the workspaces inside it.
export function getProjectActivityTimestamp(
project: DashboardSidebarProject,
): number {
const workspaces = getProjectChildrenWorkspaces(project.children);
if (workspaces.length === 0) {
const updatedAt = toTime(project.updatedAt);
return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
}
return Math.max(...workspaces.map(getWorkspaceActivityTime));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

getProjectActivityTimestamp isn't NaN-safe like its section-level counterpart.

Math.max(...workspaces.map(getWorkspaceActivityTime)) propagates NaN if any single workspace has an unparseable updatedAt and no lastAgentActivityAt. That poisons the entire project's activity timestamp, discarding every other (possibly very recent) workspace's activity and falling back to name-order sorting. getChildTimestamp's section branch (Lines 80-85) already filters out NaN before taking Math.max for exactly this reason — this function should do the same.

🐛 Proposed fix mirroring the section-level NaN filter
 export function getProjectActivityTimestamp(
 	project: DashboardSidebarProject,
 ): number {
 	const workspaces = getProjectChildrenWorkspaces(project.children);
-	if (workspaces.length === 0) {
-		const updatedAt = toTime(project.updatedAt);
-		return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
-	}
-	return Math.max(...workspaces.map(getWorkspaceActivityTime));
+	const activity = workspaces
+		.map(getWorkspaceActivityTime)
+		.filter((time) => !Number.isNaN(time));
+	if (activity.length === 0) {
+		const updatedAt = toTime(project.updatedAt);
+		return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
+	}
+	return Math.max(...activity);
 }

A regression test with one valid-timestamp workspace and one NaN-timestamp workspace in the same project would help lock this in.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The host only bumps a project's own updatedAt on metadata patches (e.g.
// rename), so "recent activity" comes from the workspaces inside it.
export function getProjectActivityTimestamp(
project: DashboardSidebarProject,
): number {
const workspaces = getProjectChildrenWorkspaces(project.children);
if (workspaces.length === 0) {
const updatedAt = toTime(project.updatedAt);
return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
}
return Math.max(...workspaces.map(getWorkspaceActivityTime));
}
// The host only bumps a project's own updatedAt on metadata patches (e.g.
// rename), so "recent activity" comes from the workspaces inside it.
export function getProjectActivityTimestamp(
project: DashboardSidebarProject,
): number {
const workspaces = getProjectChildrenWorkspaces(project.children);
const activity = workspaces
.map(getWorkspaceActivityTime)
.filter((time) => !Number.isNaN(time));
if (activity.length === 0) {
const updatedAt = toTime(project.updatedAt);
return Number.isNaN(updatedAt) ? toTime(project.createdAt) : updatedAt;
}
return Math.max(...activity);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/sortDashboardSidebarProjects/sortDashboardSidebarProjects.ts`
around lines 32 - 43, Update getProjectActivityTimestamp to filter out NaN
values from workspaces.map(getWorkspaceActivityTime) before applying Math.max,
matching the existing getChildTimestamp section-level behavior. Preserve the
fallback handling for projects without workspaces and ensure valid workspace
timestamps still determine the project activity when another workspace is
unparseable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant